﻿using System;
using System.Collections.Generic;
using System.Runtime.Serialization;
using System.ServiceModel;
using System.ServiceModel.Activation;
using System.ServiceModel.Syndication;
using System.ServiceModel.Web;
using System.Xml;
using System.Xml.Linq;
using System.Xml.Serialization;
using NCMIS.Extensions;
using NCMIS.ObjectModel.MetaData;
using NCMIS.ObjectModel.Rest;
using NCMIS.Produce;
using NCMIS.Provider;
using NCMIS.ServiceModel.Syndication;

namespace NCMIS.ServiceModel.Ra
{
    /// <summary>
    /// Provides the Repository Services for the REST/Atom binding. The repository services allows the discovery of information about
    /// a repository and the object-types defined in it.
    /// </summary>
    public partial class Service : IService
    {      
        /// <summary>
        /// Retreives the service document for a specific repository. Exposes CMIS method getRepositoryInfo.
        /// </summary>
        /// <returns>The service document formatted for AtomPub.</returns>
        [WebGet(UriTemplate = "/{repositoryId}/servicedoc")]
        public AtomPub10ServiceDocumentFormatter GetServiceDocument(string repositoryId)
        {
            OutgoingWebResponseContext ctx = WebOperationContext.Current.OutgoingResponse;
            ctx.ContentType = CmisContentType.Service;

            CmisServiceDocument serviceDocument = new CmisServiceDocument();

            RepositoryEntry[] repositories = null;
            // If there are currently no repositories, retrieve them
            if (String.IsNullOrEmpty(repositoryId))
            {
                repositories = RepositoryProviderManager.Provider.GetRepositories();
            }
            // If the repository exists, retrieve it
            else
            {
                repositories = new RepositoryEntry[]
                {
                    RepositoryProviderManager.Provider.GetRepository(repositoryId),
                };
            }

            // Add repositories to service document
            foreach (RepositoryEntry repository in repositories)
            {
                RepositoryInfo repositoryInfo = RepositoryProviderManager.Provider.GetRepositoryInfo(repository.Id);
                serviceDocument.Workspaces.Add(new CmisWorkspace(repository.Id, repository.Name, _baseUri, repositoryInfo));
            }

            // Format the service doc as an Atom Service Document
            return serviceDocument.GetFormatter() as AtomPub10ServiceDocumentFormatter;
        }

        /// <summary>
        /// Retrieves the collection (Atom Feed) with base types of a repository.
        /// </summary>
        /// <param name="repositoryId">The id of the repository.</param>
        /// <param name="typeId">The type id of an object-type to retreive children for. (optional, default: {baseType id})</param>
        /// <param name="includePropertyDefinitions">If true propertydefintions will be returned for each returned object-type. (optional, default: false)</param>
        /// <param name="maxItems">The maximum number of items to return in the response. (optional, default: repository specific)</param>
        /// <param name="skipCount">The number of potential results to skip before returning any results. (optional, default: 0)</param>
        /// <returns>The repository's base types formatted as Atom 1.0 Feed.</returns>
        [WebGet(UriTemplate = "/{repositoryId}/types?typeId={typeId}&includePropertyDefinitions={includePropertyDefinitions}&maxItems={maxItems}&skipCount={skipCount}")]
        public Atom10FeedFormatter GetTypeChildren(string repositoryId, string typeId, bool includePropertyDefinitions, int maxItems, int skipCount)
        {
            // Repository specific parameters are sent as null values to provider 
            int? _maxItems = (maxItems == 0) ? (int?)null : maxItems;

            OutgoingWebResponseContext ctx = WebOperationContext.Current.OutgoingResponse;
            ctx.ContentType = CmisContentType.Feed;

            // Repository ID must be specified   
            if (String.IsNullOrEmpty(repositoryId))
            {
                ctx.StatusCode = ServiceException.InvalidArgument;
                ctx.StatusDescription = String.Format("{0}: The parameters repositoryId must not be null or empty.",
                    ServiceException.InvalidArgument.ToString());
                return null;
            }

            // Store the type children in a typeDefinitionList
            TypeDefinitionList typeDefinitionList = RepositoryProviderManager.Provider.GetTypeChildren(repositoryId, typeId, includePropertyDefinitions, _maxItems, skipCount);

            // Create a feed to hold the list content
            CmisSyndicationFeed feed = new CmisSyndicationFeed()
            {
                BaseUri = _baseUri,
                Id = string.Format("urn:uuid:{0}-types", typeId),
                RepositoryId = repositoryId,
                SelfLinkUri = new Uri(_baseUri, String.Format("{0}/types{1}", repositoryId, HttpRequest.GetQueryString())),
                ServiceLinkUri = new Uri(_baseUri, String.Format("{0}/servicedoc", repositoryId)),
                Title = new TextSyndicationContent(string.Format("{0} Types", typeId)),
            };
            feed.AddEntries(typeDefinitionList);
            feed.AddNumItems();

            // Format the feed as an Atom feed
            return new Atom10FeedFormatter(feed);
        }

        /// <summary>
        /// Retreives the collection (Atom Feed) with type descendants for a repository type.
        /// </summary>
        /// <param name="repositoryId">The id of the repository.</param>
        /// <param name="typeId">The type id of an object-type to retreive children for. (optional, default: {all types})</param>
        /// <param name="depth">The number of levels of depth in the type hierachy from which to return results. (Optional, default: repository specific)</param>
        /// <param name="includePropertyDefinitions">If true propertydefintions will be returned for each returned object-type. (Optional, default: false)</param>
        /// <returns>The specified type's descendants formatted as Atom 1.0 Feed.</returns>
        [WebGet(UriTemplate = "/{repositoryId}/typedescendants/{typeId}?depth={depth}&includePropertyDefinitions={includePropertyDefinitions}")]
        public Atom10FeedFormatter GetTypeDescendants(string repositoryId, string typeId, int depth, bool includePropertyDefinitions)
        {
            // Repository specific parameters are sent as null values to provider 
            int? _depth = (depth == 0) ? (int?)null : depth;

            OutgoingWebResponseContext ctx = WebOperationContext.Current.OutgoingResponse;
            ctx.ContentType = CmisContentType.Feed;

            // Repository ID and type ID must be specified
            if (String.IsNullOrEmpty(repositoryId) || String.IsNullOrEmpty(typeId))
            {
                ctx.StatusCode = ServiceException.InvalidArgument;
                ctx.StatusDescription = String.Format("{0}: The parameters repositoryId and typeId must not be null or empty.",
                    ServiceException.InvalidArgument.ToString());
                return null;
            }

            // Store the type descendants in a typeContainer 
            TypeContainer[] typeDescendants = RepositoryProviderManager.Provider.GetTypeDescendants(repositoryId, typeId, _depth, includePropertyDefinitions);

            // Create a feed to hold the type descendants
            CmisSyndicationFeed feed = new CmisSyndicationFeed()
            {
                BaseUri = _baseUri,
                SelfLinkUri = new Uri(_baseUri, String.Format("{0}/typedescendants", repositoryId)),
                ServiceLinkUri = new Uri(_baseUri, String.Format("{0}/servicedoc", repositoryId)),
                RepositoryId = repositoryId,
                Title = new TextSyndicationContent(string.Format("Type {0} Descendants", typeId))
            };
            feed.AddEntries(typeDescendants);
            feed.AddNumItems();

            // Format the feed as an Atom feed
            return new Atom10FeedFormatter(feed);
        }

        /// <summary>
        /// Retreives the type definition Atom Feed.
        /// </summary>
        /// <param name="repositoryId">The id of the repository</param>
        /// <param name="typeId">the type id of the object-type to retrieve descendants for.</param>
        /// <returns>The type descendants for specified type formatted as Atom 1.0 Entry.</returns>
        [WebGet(UriTemplate = CmisUriTemplate.TypeById)]
        public Atom10ItemFormatter GetTypeDefinition(string repositoryId, string typeId)
        {
            OutgoingWebResponseContext ctx = WebOperationContext.Current.OutgoingResponse;
            ctx.ContentType = CmisContentType.Entry;

            // Repository ID and type ID must be specified
            if (String.IsNullOrEmpty(repositoryId) || String.IsNullOrEmpty(typeId))
            {
                ctx.StatusCode = ServiceException.InvalidArgument;
                ctx.StatusDescription = String.Format("{0}: The parameters repositoryId and typeId must not be null or empty.",
                    ServiceException.InvalidArgument.ToString());
                return null;
            }

            // NOTE: typeId used in URL, uses underscore instead of colon, e.g. "cmis_folder" instead of "cmis:folder".
            typeId = typeId.Replace('_', ':');

            // Get the type definition
            TypeDefinition typeDefinition = RepositoryProviderManager.Provider.GetTypeDefinition(repositoryId, typeId);
            
            // Convert the type definition to a syndication item
            CmisSyndicationItem item = CmisSyndicationItem.CreateEntry(repositoryId, typeDefinition);

            // Format the syndication item as an Atom entry
            return new Atom10ItemFormatter(item);
        }
    }
}
